package com.zengqingfa.springboot.mybatis.demo.controller;

import cn.hutool.core.util.RandomUtil;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import com.zengqingfa.springboot.mybatis.demo.core.BloomFilterHelper;
import com.zengqingfa.springboot.mybatis.demo.core.RedisBloomFilter;
import com.zengqingfa.springboot.mybatis.demo.entity.NullEntity;
import com.zengqingfa.springboot.mybatis.demo.entity.User;
import com.zengqingfa.springboot.mybatis.demo.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {

    private AtomicInteger count = new AtomicInteger();


    @Resource
    private UserService userService;

    @Resource//不能使用@Autowired，如果非要使用，不注明泛型类型即可
    private RedisTemplate<String, Object> redisTemplate;

    public static final String REDIS_USER_KEY = "user:id:";
    public static final String REDIS_USER_LOCK_KEY = "lock:user:id:";

    @PostMapping("/add")
    public void add(@RequestBody User user) {
        userService.add(user);
    }

    @GetMapping("/list")
    public List<User> list() {
        return userService.getList();
    }


    /**
     * 正常操作
     * @param id
     * @return
     */
    @GetMapping("/getById")
    public User getById(@RequestParam("id") String id) {
        Object object = redisTemplate.opsForValue().get(REDIS_USER_KEY + id);
        if (object == null) {
            log.info("查询数据库");
            User user = userService.getById(id);
            if (Objects.nonNull(user)) {
                redisTemplate.opsForValue().set(REDIS_USER_KEY + user.getId(), user);
                return user;
            } else {
                return null;
            }
        }
        return (User) object;
    }

    /**缓存穿透
     * 解决方式一：缓存空对象
     * 缓存空对象会带来比较大的问题，就是缓存中会存在很多空对象，
     * 占用内存的空间，浪费资源，一个解决的办法就是设置空对象的较短的过期时间
     * @param id
     * @return
     */
    @GetMapping("/getById-1")
    public User getById_1(@RequestParam("id") String id) {
        Object object = redisTemplate.opsForValue().get(REDIS_USER_KEY + id);
        if (object != null) {
            // 检验该对象是否为缓存空对象，是则直接返回null
            if (object instanceof NullEntity) {
                return null;
            }
            return (User) object;
        } else {
            log.info("查询数据库");
            User user = userService.getById(id);
            if (Objects.nonNull(user)) {
                redisTemplate.opsForValue().set(REDIS_USER_KEY + id, user);
            } else {
                redisTemplate.opsForValue().set(REDIS_USER_KEY + id, new NullEntity(), 60, TimeUnit.SECONDS);
            }
            return user;
        }
    }


    private static final double fpp = 0.03;
    private static BloomFilter<Integer> bf;

    @PostConstruct
    public void init1() {
        List<User> list = list();
        if (list.size() < 1) {
            return;
        }
        bf = BloomFilter.create(Funnels.integerFunnel(), list.size(), fpp);
        // 初始化所有的用户id到过滤器中
        for (int i = 0; i < list.size(); i++) {
            bf.put(Integer.valueOf(list.get(i).getId()));
        }
    }

    /**缓存穿透
     * 解决方式二：布隆过滤器（guava）
     * @param id
     * @return
     */
    @GetMapping("/getById-2")
    public User getById_2(@RequestParam("id") Integer id) {
        //判断布隆过滤器是否存在
        if (!bf.mightContain(id)) {
            return null;
        }
        return getUser(REDIS_USER_KEY + id, String.valueOf(id));
    }


    @Autowired
    private RedisBloomFilter redisBloomFilter;

    private static BloomFilterHelper<Integer> bfRedis;

    @PostConstruct
    public void init2() {
        List<User> list = list();
        if (list.size() < 1) {
            return;
        }
        bfRedis = new BloomFilterHelper<>(Funnels.integerFunnel(), list.size(), fpp);
        // 初始化所有的用户id到过滤器中
        for (int i = 0; i < list.size(); i++) {
            redisBloomFilter.put(bfRedis, list.get(i).getId(), Integer.valueOf(list.get(i).getId()));
        }
    }

    /**缓存穿透
     * 解决方式三：布隆过滤器（guava+redis）
     * @param id
     * @return
     */
    @GetMapping("/getById-3")
    public User getById_3(@RequestParam("id") String id) {
        //判断布隆过滤器是否存在
        if (!redisBloomFilter.contains(bfRedis, id, Integer.valueOf(id))) {
            return null;
        }
        return getUser(REDIS_USER_KEY + id, id);
    }

    /**
     * 获取用户
     * @param s
     * @param value
     * @return
     */
    private User getUser(String s, String value) {
        Object object = redisTemplate.opsForValue().get(REDIS_USER_KEY + s);
        if (object != null) {
            return (User) object;
        } else {
            log.info("查询数据库");
            User user = userService.getById(value);
            if (Objects.nonNull(user)) {
                redisTemplate.opsForValue().set(REDIS_USER_KEY + s, user);
            }
            return user;
        }
    }


    @Autowired
    private RedissonClient redissonClient;

    /**
     * 缓存击穿方案：加锁
     * @param id
     * @return
     */
    @GetMapping("/getById-4")
    public User getById_4(@RequestParam("id") String id) {
        Object object = redisTemplate.opsForValue().get(REDIS_USER_KEY + id);
        if (object == null) {
            User user = null;
            RLock lock = redissonClient.getLock(REDIS_USER_LOCK_KEY + id);
            try {
                // 1. 最常见的使用方法
                //lock.lock();

                // 2. 支持过期解锁功能,10秒钟以后自动解锁, 无需调用unlock方法手动解锁
                //lock.lock(10, TimeUnit.SECONDS);

                // 3. 尝试加锁，最多等待3秒，上锁以后10秒自动解锁
                boolean flag = lock.tryLock(3, 10, TimeUnit.SECONDS);
                if (flag) {
                    log.info("查询数据库");
                    user = userService.getById(id);
                    if (Objects.nonNull(user)) {
                        redisTemplate.opsForValue().set(REDIS_USER_KEY + id, user);
                    }
                    return user;
                }
            } catch (Exception e) {
                log.error("e={}", e.getMessage());
            } finally {
                lock.unlock();
            }
            return user;
        }
        return (User) object;
    }


    private static final Long EXPIRE_TIME = 24 * 60 * 60L;

    /**
     * 缓存雪崩方案：设置随机的过期时间
     * @param id
     * @return
     */
    @GetMapping("/getById-5")
    public User getById_5(@RequestParam("id") String id) {
        Object object = redisTemplate.opsForValue().get(REDIS_USER_KEY + id);
        if (object == null) {
            log.info("查询数据库");
            User user = userService.getById(id);
            if (Objects.nonNull(user)) {
                redisTemplate.opsForValue()
                        .set(REDIS_USER_KEY + id, user, EXPIRE_TIME + RandomUtil.randomInt(60 * 5, 2 * 60 * 60),
                                TimeUnit.SECONDS);

            }
            return user;
        }
        return (User) object;
    }


}
